EC2 Image Builder で作られたイメージ(AMI)を自動的に SSMパラメータストアに格納する
はじめに
EC2 Image Builder(以下 Image Builder)のパイプラインから EC2イメージ(AMI)を作成したときに、 そのAMI IDを Systems Manager(SSM)パラメータストアに自動的に格納するような構成を試してみます。
図で表すと以下のとおりです。
- Image Builderパイプライン で AMI作成時に SNSトピックを通知するように設定しておきます
- このSNSトピック通知をトリガーに Lambda関数を実行します
- このLambda関数で SSMパラメータストアに AMI IDの情報を登録(
PutParameter
)します
なお、今回試した構成は以下 AWSブログを参考にしています。
構成
EC2 Image Builder
パイプラインやレシピの詳細説明は割愛します。
パイプラインの インフラストラクチャ設定 > SNS 部分を設定しておきます。
SNS設定により、ImageBuilder で AMI作成が完了したときに、以下のようなメッセージを通知することができます。 (※メッセージサンプル全文はブログの最後に記載)
{ "versionlessArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe", "semver": 1073741827, "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3", "name": "sample-recipe", (略) "state": { "status": "AVAILABLE" }, (略) "outputResources": { "amis": [ { "region": "ap-northeast-1", "image": "ami-01xxxxxxxxxxxxxxx", "name": "sample-recipe 2021-03-05T01-16-05.489Z", "accountId": "123456789012" } ] }, (略) }
ハイライト部分(レシピ名, ステータス, AMI ID)を後述の Lambda関数で利用します。
Lambda関数
今回は ランタイム: Python3.7
で作成しています。
以下のようなコードを作成しました。
import json import boto3 import logging logger = logging.getLogger() logger.setLevel(logging.INFO) ssm = boto3.client('ssm') def lambda_handler(event, context): message = json.loads(event["Records"][0]["Sns"]["Message"]) process_sns_message(message) def process_sns_message(message): logger.info("Printing message: {}".format(message)) # state キーが無いとき、もしくは state.status が "AVAILABLE" でないときはパラメータストアに登録しない if message.get('state') == None or message['state'].get('status') != "AVAILABLE": return None # レシピ名とAMI IDを取得 recipe_name = message['name'] ami = message['outputResources']['amis'][0] logger.info("recipe_name={}".format(recipe_name)) logger.info("ami={}".format(ami)) # SSMパラメータストアに登録 response = ssm.put_parameter( Name="/ec2-imagebuilder/latest/{}".format(recipe_name), Description="Latest AMI ID:{}".format(recipe_name), Value=ami['image'], Type='String', Overwrite=True, Tier='Standard' ) logger.info("Printing ssm.put_parameter response: {}".format(response))
受け取った SNSメッセージをのステータス、AMI ID、レシピ名を取得して、
SSMパラメータ ( "/ec2-imagebuilder/latest/{レシピ名}"
) に登録するシンプルな処理です。
▼ ほか設定: SNS
Image Builder のインフラストラクチャ設定で指定したSNSトピックをトリガーとします。
▼ ほか設定: IAMロール
実行ロールに必要なポリシーは以下のとおり
AWSLambdaBasicExecutionRole
(AWS管理ポリシー)- SSMパラメータに値を入れるアクション(
ssm:PutParameter
)を許可するポリシー
後者は以下のようなポリシーです。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Resource": "*", "Action": "ssm:PutParameter" } ] }
SAMで構築
上記構成 (SNSトピックとLambda関数の部分)を AWS サーバーレスアプリケーションモデル (AWS SAM)を使って構築しました。 SAMのプロジェクト構成内容を記します。
プロジェクト
sam init
で新規プロジェクトを作成します。
sam init --runtime python3.7 --name tracking-latest-images-in-imagebuilder
template.yaml
必要なリソースを記述した template.yaml
は以下のとおり。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: "tracking the latest AMI ID in EC2 Image Builder pipeline" Resources: # SNS topic SnsTopic: Type: AWS::SNS::Topic Properties: TopicName: topic-for-imagebuilder # Lambda function Function: Type: AWS::Serverless::Function Properties: Description: "Update SSM Parameter with the latest AMI ID" CodeUri: scripts/ Handler: app.lambda_handler Runtime: python3.7 Events: EventBridgeRule: Type: SNS Properties: Topic: !Ref SnsTopic Policies: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - Version: '2012-10-17' Statement: - Effect: Allow Action: - ssm:PutParameter Resource: '*'
scripts/app.py
前述のLambda関数のコードを scripts/app.py
に格納します。
ビルド, デプロイ
sam build sam deploy --guided
特にパラメータ指定していないので、ガイド通りに YES 選択でデプロイできます。
確認
1回目
まず ImageBuilderのパイプラインの インフラストラクチャ設定 > SNS
部分に、前述で作成した
SNSトピックを指定します。
そのパイプラインを実行してみます。
[使用可能]
になるまで待ちましょう。
[使用可能]
となった段階で SSMパラメータストアを確認します。
先程 ImageBuilderパイプラインで作成した AMIの IDが登録されていることを確認できました。
2回目以降
再度 同じパイプラインを実行してみます。
値が上書きされたことを確認できました。
おわりに
EC2 Image Builder で作った最新AMIをSSMパラメータストアに自動登録する仕組みを作ってみました。 CloudFormation で EC2インスタンスを作成する際の AMI ID 参照などに活用できると思います。
参考
- AWS
- Boto3
ImageBuilder の通知メッセージサンプル
{ "versionlessArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe", "semver": 1073741827, "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3", "name": "sample-recipe", "version": "0.0.1", "type": "AMI", "buildVersion": 3, "state": { "status": "AVAILABLE" }, "platform": "Linux", "imageRecipe": { "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image-recipe/sample-recipe/0.0.1", "name": "sample-recipe", "version": "0.0.1", "components": [ { "componentArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:component/sample-test/0.0.1/1" }, { "componentArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:component/sample-build/0.0.1/1" } ], "platform": "Linux", "parentImage": "arn:aws:imagebuilder:ap-northeast-1:255485124026:image/amazon-linux-2-x86/2021.2.20/2", "blockDeviceMappings": [ { "deviceName": "/dev/xvda", "ebs": { "encrypted": false, "deleteOnTermination": true, "volumeSize": 8, "volumeType": "gp2" } } ], "dateCreated": "Oct 27, 2020 2:34:30 AM", "tags": { "internalId": "52b73813-4fed-4a5a-a64d-22521554d366", "internalId": "52xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image-recipe/sample-recipe/0.0.1" }, "accountId": "123456789012" }, "sourcePipelineArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image-pipeline/sample-al2-ami", "infrastructureConfiguration": { "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:infrastructure-configuration/sample-infra-conf", "name": "sample-infra-conf", "instanceTypes": [ "t3.small" ], "instanceProfileName": "EC2InstanceProfileForImageBuilder", "securityGroupIds": [ "sg-xxxxxxxxxxxxxxxxx" ], "subnetId": "subnet-xxxxxxxx", "tags": { "internalId": "30xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:infrastructure-configuration/sample-infra-conf" }, "logging": { "s3Logs": {} }, "terminateInstanceOnFailure": true, "snsTopicArn": "arn:aws:sns:ap-northeast-1:123456789012:topic-for-imagebuilder", "dateCreated": "Mar 5, 2021 1:14:49 AM", "accountId": "123456789012" }, "imageTestsConfigurationDocument": { "imageTestsEnabled": true, "timeoutMinutes": 720 }, "distributionConfiguration": { "arn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-6b98ab10-8065-42e0-9954-975621d6258c", "name": "sample-al2-ami-6b98ab10-8065-42e0-9954-975621d6258c", "dateCreated": "Mar 5, 2021 1:14:50 AM", "distributions": [ { "region": "ap-northeast-1", "amiDistributionConfiguration": {} } ], "tags": { "internalId": "6520c90f-86ef-4a01-9143-6fee8079d1e8", "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:distribution-configuration/sample-al2-ami-6b98ab10-8065-42e0-9954-975621d6258c" }, "accountId": "123456789012" }, "dateCreated": "Mar 5, 2021 1:15:31 AM", "outputResources": { "amis": [ { "region": "ap-northeast-1", "image": "ami-013e36332e677ed78", "image": "ami-01xxxxxxxxxxxxxxx", "name": "sample-recipe 2021-03-05T01-16-05.489Z", "accountId": "123456789012" } ] }, "buildExecutionId": "7cba5e41-3d86-4b3e-8f01-e0f8edd36371", "testExecutionId": "0d103efb-0755-4465-b6f0-3af0ce93d407", "distributionJobId": "40e757ba-575b-4314-8e38-21a1cba8535e", "integrationJobId": "e8eb11b2-6a63-4aaa-ab1b-685bae925833", "accountId": "123456789012", "enhancedImageMetadataEnabled": false, "tags": { "internalId": "6041d8a9-3491-4a03-a1b6-bf92c83227a3", "resourceArn": "arn:aws:imagebuilder:ap-northeast-1:123456789012:image/sample-recipe/0.0.1/3" } }